Skip to content

feat(opencode): add killswitch indicators to quota toast#38

Open
iceteaSA wants to merge 8 commits into
cortexkit:mainfrom
iceteaSA:feat/killswitch-toast
Open

feat(opencode): add killswitch indicators to quota toast#38
iceteaSA wants to merge 8 commits into
cortexkit:mainfrom
iceteaSA:feat/killswitch-toast

Conversation

@iceteaSA
Copy link
Copy Markdown
Contributor

@iceteaSA iceteaSA commented May 21, 2026

Dependencies: Requires #35 (killswitch) and #36 (quota-toast) to be merged first.

Adds killswitch awareness to quota toast notifications:

  • 🔴 red dot for killed accounts
  • 🟢 green dot for active/routable accounts
  • Killswitch-aware active account selection skips killed accounts

Single file change: packages/opencode/src/index.ts


Summary by cubic

Adds killswitch-aware quota toasts with active/idle/blocked status and unifies quota fetching behind a single QuotaManager for main and fallbacks. Blocks low‑quota accounts via a synthetic 429 with Retry‑After and adds /claude-killswitch for thresholds and /claude-quota for forced refresh.

  • New Features

    • Quota toast: status words (active/idle/blocked), usage bars, reset times, “7d” label; auto‑shown after refresh.
    • Killswitch: per‑account thresholds (5h/7d), eager refresh on first request; skip killed accounts (including main); synthetic 429 + Retry‑After when all accounts are below thresholds; /claude-killswitch to view/toggle/set thresholds and show usage.
    • QuotaManager: single path for main+fallback; inflight dedupe; seeds from persisted snapshots bound to a token fingerprint; backoff only on 429/5xx (not 401); every‑N request refresh; /claude-quota forces/persists refresh.
  • Bug Fixes

    • Bound persisted main quota seed to a token fingerprint to avoid serving stale quota after account switches during backoff.
    • Scoped quota‑API backoff per route: fallback 429s no longer back off main, and main 429s don’t affect fallbacks.
    • Count all replayable requests to drive the every‑N refresh cadence consistently across routing paths.

Written for commit 7ab6592. Summary will update on new commits.

Review in cubic

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 10 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Fix all with cubic | Re-trigger cubic

Comment thread packages/core/src/quota-manager.ts
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch 5 times, most recently from 16c1a8b to b6cc376 Compare May 22, 2026 17:08
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch 2 times, most recently from 2b3d169 to b5fd4aa Compare June 1, 2026 07:03
Unified quota cache + API gateway for main and fallback quota. All consumers
share one QuotaManager instance for consistent caching, inflight dedup, and
429 backoff.

- One quota path: FallbackAccountManager.refreshAccountQuota() now routes
  through QuotaManager.refreshFallback() (dedup + backoff) instead of calling
  fetchOAuthQuotaSnapshot() directly. Falls back to a direct fetch only when
  no QuotaManager is wired.
- 401 is treated as auth/token expiry, not rate-limiting: it no longer arms
  quota backoff, so the caller's token-refresh-and-retry (and other accounts)
  are not blocked.
- /claude-quota forces a real refresh for main (refreshMain) and all fallbacks
  (refreshQuotaForAllAccounts({ force: true })) and PERSISTS the refreshed
  fallback snapshots to anthropic-auth.json, clearing stale errors. 429 backoff
  still respected (returns last cached numbers when backed off).
- Main quota refresh is wired to the request path: needsRefresh(requestCount)
  drives an every-N background refresh (quota.refreshEveryNRequests), matching
  the existing time-staleness trigger.
- Fallback quota refreshes proactively too: the existing background timer ticks
  refreshQuotaForDueAccounts(), and the active fallback route refreshes on the
  same every-N cadence at its markUsed() point (idle fallbacks untouched).
- getUsableFallbackAccounts() skips the request-time refresh when an account's
  quota API is backed off, mirroring the background path (no API hammering).
- Seeds from persisted storage on construction to avoid cold-start API calls.

The main-account fail-closed killswitch (synthetic 429 when quota API is
backed off) is intentionally NOT in this base — it moves to feat/killswitch
where it belongs, keeping quota-manager a pure routing optimization that never
blocks the main request.
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch 3 times, most recently from 7a2166e to 7509642 Compare June 1, 2026 10:44
iceteaSA added 7 commits June 1, 2026 13:45
…equests

Two AI-review fixes:

- Token-bind the persisted main quota seed. The constructor seeded this.main
  from storage without recording which account produced it, so refreshMain's
  token-change guard had no baseline and a different account's stale quota
  could be served during backoff. Persist a token fingerprint (truncated
  SHA-256) with the main quota, seed it alongside this.main, and invalidate the
  seed in refreshMain before the backoff short-circuit when the live token's
  fingerprint differs.

- Increment sessionRequestCount for every replayable request instead of only
  inside the mainQuotaRoutingEnabled branch, so the every-N refresh cadence
  (and the active-route fallback refresh) advances on all routing paths.

Adds regression tests for seed drop/keep on token change during backoff.
A single shared lastApiError meant a fallback account's quota 429 backed off
the main account and persisted as the main quota error: _fetchFallback called
the shared error handler, which set lastApiError and invoked onApiError (which
the OpenCode plugin persists as storage.quota.mainLastQuotaApiError); refreshMain
then short-circuited via isBackedOff() and threw without ever trying the main
token.

Scope backoff state by route:
- mainLastApiError drives isBackedOff()/getLastApiError() and refreshMain; only
  main failures arm it and call onApiError.
- fallbackApiErrors is a per-account map driving the new isFallbackBackedOff();
  fallback failures arm only that account's backoff and never touch main state
  or onApiError (the per-account error is already recorded via the account's
  lastQuotaRefreshError).

Adds regression tests: fallback 429 does not back off main or fire onApiError,
and main 429 does not back off a fallback.
…reshold

Per-account request blocking when remaining quota drops below configurable
thresholds. Returns synthetic 429 when all accounts (main + fallbacks) are
below their thresholds. Includes /claude-killswitch slash command for
runtime management.

Features:
- Per-account threshold overrides (5h and 7d windows)
- Eager quota refresh on first request for killswitch evaluation
- Skip-main routing when main is killed (try surviving fallbacks)
- Filter killed accounts from reactive fallback path
- Retry-After header with earliest quota reset time
Displays quota usage bar notifications via client.tui.showToast after
quota data is refreshed. Shows main and fallback account usage with
visual bars, percentage, and reset time. Toast variant reflects
severity (info < 70%, warning >= 70%, error >= 90%).
Align the quota refresh toast with the sidebar's visual language:

- Replace the emoji status dots with status words (active/idle)
- Use the shared bar width and a padded percentage via a quotaLine helper
- Rename the seven-day label from 1w to 7d to match the sidebar
- Keep severity-driven variant color (info/warning/error)
Layer killswitch awareness onto the restyled toast:

- Add an isKilled predicate param to showQuotaToast
- Render a blocked status word for killed accounts (alongside active/idle)
- showQuotaToastFromCache derives killed state from killswitchPassesPolicy,
  skips killed accounts when picking the active route, and forwards the
  predicate to the toast

Kept separate from the base toast so the non-killswitch path carries no
killswitch code.
@iceteaSA iceteaSA force-pushed the feat/killswitch-toast branch from 7509642 to 7ab6592 Compare June 1, 2026 11:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant